// Copyright 2010, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef SCRW_BASE_64_H_
#define SCRW_BASE_64_H_

#include <string>
#include <vector>
#include <libv/cast.h>
#include <libv/check.h>
#include <libv/stdint.h>


namespace scrw
{

/** Эффективное кодирование массива байт в base64.
  * Алгоритм base64 позволяет закодировать массив байт в подмножество ASCII,
  * увеличив размер результата на треть (3 байта кодируются 4 символами).
  * Основная идея алгоритма состоит в следующем:
  * берем 3 байта (24 бита) и разделяем их на 4 6-битных значения.
  * Данные значения (числа в 64-ричной системе счисления) добавляем в результат.
  *
  * Обычные реализации кодируют каждое значение по таблице.
  * То есть на каждые 3 байта приходится 4 кодирования.
  * Основная оптимизация данной реализации состоит в использовании таблицы
  * кодирования сразу 1,5 байт (2 значений).
  * Такая таблица вместо 64 байт занимает 8 кб, что вполне приемлемо.
  * Таблица инициализируется в конструкторе.
  *
  * Основано на статье: http://www.experts-exchange.com/Programming/System/Windows__Programming/A_3216-Fast-Base64-Encode-and-Decode.html */
class Base64
{
  public:
    /** Конструктор, инициализирует таблицу преобразований. */
    Base64()
    {
        InitEncodeTables();
    }


    /** Кодирование входного массива байт алгоритмом base64. */
    ::std::string Encode(const ::std::vector<char>& data) const
    {
        if ( data.empty() )
            return ::std::string();

        return Encode(&data[0], data.size());
    }


    ::std::string Encode(const char* data, const size_t data_size) const
    {
        PRECOND(data != NULL);
        if ( data_size == 0 )
            return ::std::string();

        ::std::vector<char> v((data_size + 2) / 3 * 4);

        const uint8_t* src_pos = libv::PointerCast<const uint8_t>(data);
        const uint8_t* src_end = src_pos + data_size;
        uint16_t* dst16 = libv::PointerCast<uint16_t>(&v[0]);

        for ( ; src_end - src_pos > 2 ; src_pos += 3, dst16 += 2 )
        {
            const size_t n = (src_pos[0] << 16) + (src_pos[1] << 8) +src_pos[2];

            dst16[0] = e_table16_[n >> 12];
            dst16[1] = e_table16_[n & 0x00000fff];
        }


        char* dst = libv::PointerCast<char>(dst16);
        const ptrdiff_t src_left = src_end - src_pos;

        if ( src_left == 2 )
        {
            // Осталось 2 байта: это 6 * 2 + 4 бита
            // Первые 6 бит 1-го байта
            const int n1 = (src_pos[0] & 0xfc) >> 2;

            // Последние 2 бита 1-го байта и первые 4 бита 2-го байта
            const int n2 = ( (src_pos[0] & 0x03) << 4 ) |
                           ( (src_pos[1] & 0xf0) >> 4 );

            // Последние 4 бита 2-го байта
            const int n3 = (src_pos[1] & 0x0f) << 2;

            *dst++ = e_table8_[n1];
            *dst++ = e_table8_[n2];
            *dst++ = e_table8_[n3];
            *dst++ = '=';
        }

        else if ( src_left == 1 )
        {
            *dst++ = e_table8_[(src_pos[0] & 0xfc) >> 2];
            *dst++ = e_table8_[(src_pos[0] & 0x03) << 4];
            *dst++ = '=';
            *dst++ = '=';
        }

        assert(static_cast<size_t>(dst - &v[0]) == v.size());
        return ::std::string(v.begin(), v.end());
    }

  private:
    void InitEncodeTables()
    {
        const char table8_init[] =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        e_table8_.reserve(64);
        e_table8_.insert(e_table8_.end(), table8_init, table8_init + 64);


        e_table16_.reserve(64*64);
        e_table16_.resize(64*64);

        for ( size_t i = 0; i != 64; ++i )
            for ( size_t j = 0 ; j != 64 ; ++j )
                e_table16_[(i * 64) + j] = (e_table8_[j] << 8) + e_table8_[i];
    }


    ::std::vector<char> e_table8_;
    ::std::vector<int16_t> e_table16_;
};

}  // scrw

#endif  // SCRW_BASE_64_H_
